package combinatorics;

import java.util.Arrays;

// https://en.wikipedia.org/wiki/Combination
public class Combinations {
    public static boolean nextCombination(int[] comb, int n) {
        int k = comb.length;
        for (int i = k - 1; i >= 0; i--) {
            if (comb[i] < n - k + i) {
                ++comb[i];
                while (++i < k) {
                    comb[i] = comb[i - 1] + 1;
                }
                return true;
            }
        }
        return false;
    }

    public static int[] combinationByNumber(int n, int k, long number) {
        int[] c = new int[k];
        int cnt = n;
        for (int i = 0; i < k; i++) {
            int j = 1;
            while (true) {
                long am = binomial(cnt - j, k - 1 - i);
                if (number < am)
                    break;
                number -= am;
                ++j;
            }
            c[i] = (i > 0) ? (c[i - 1] + j) : (j - 1);
            cnt -= j;
        }
        return c;
    }

    public static long numberByCombination(int[] c, int n) {
        int k = c.length;
        long res = 0;
        int prev = -1;
        for (int i = 0; i < k; i++) {
            for (int j = prev + 1; j < c[i]; j++) {
                res += binomial(n - 1 - j, k - 1 - i);
            }
            prev = c[i];
        }
        return res;
    }

    static long binomial(long n, long k) {
        k = Math.min(k, n - k);
        long res = 1;
        for (long i = 0; i < k; i++) {
            res = res * (n - i) / (i + 1);
        }
        return res;
    }

    public static boolean nextCombinationWithRepeats(int[] p, int n) {
        int k = p.length;
        for (int i = k - 1; i >= 0; i--) {
            if (p[i] < n - 1) {
                ++p[i];
                while (++i < k) {
                    p[i] = p[i - 1];
                }
                return true;
            }
        }
        return false;
    }

    // Usage example
    public static void main(String[] args) {
        int[] p = {0, 1};
        System.out.println(!nextCombination(p, 2));
        System.out.println(Arrays.equals(new int[] {0, 1}, p));

        p = new int[2];
        System.out.println(nextCombinationWithRepeats(p, 2));
        System.out.println(Arrays.equals(new int[] {0, 1}, p));

        System.out.println(nextCombinationWithRepeats(p, 2));
        System.out.println(Arrays.equals(new int[] {1, 1}, p));

        System.out.println(!nextCombinationWithRepeats(p, 2));

        System.out.println(78 == numberByCombination(new int[] {1, 2, 3, 6, 8}, 9));
        System.out.println(Arrays.toString(combinationByNumber(9, 5, 78)));

        p = new int[3];
        do {
            System.out.println(Arrays.toString(p));
        } while (nextCombinationWithRepeats(p, 3));
    }
}
